#include <cstdint>
#include <fstream>
#include <iostream>
#include <sstream>
#include <iomanip>

#include <sys/ptrace.h>
#include <sys/user.h>
#include <unistd.h>
#include <sys/wait.h>

static pid_t pid;

struct asmInstruction {
    uint64_t start;
    uint64_t end;
    uint64_t offset;
    char name[256];
    uint64_t rip;
    uint64_t ins;
};

bool catMemoryAreaOffset(struct asmInstruction &ins)
{
    std::stringstream maps;
    maps << "/proc/" << pid << "/maps";
    std::ifstream ifs(maps.str().c_str());
    char line[256];

    while (ifs.getline(line, sizeof(line))) {
        ins.name[0] = 0;
        sscanf(line, "%lx-%lx %*s %lx %*s %*s %s", &ins.start, &ins.end, &ins.offset, ins.name);
        if (ins.start <= ins.rip && ins.rip < ins.end) {
            return true;
        }
    }
    return false;
}

bool printSource(uint64_t rip, int frameid)
{
    struct asmInstruction ins = { .rip = rip };
    ins.ins = ptrace(PTRACE_PEEKTEXT, pid, rip, nullptr);

    if (!catMemoryAreaOffset(ins)) {
        std::cout << "unexpected: cannot found in maps" << std::endl;
        return false;
    }

    if (ins.name[0] == 0) {
        std::cout << "unexpected: noname code" << std::endl;
        return false;
    }

    std::cout << "#" << std::noshowbase << std::dec << std::setw(2) << std::setfill('0') << frameid << " "
        << "rip: " << std::hex << std::setw(8) << std::setfill('0') << (ins.offset + ins.rip - ins.start + 1) << " "
        << ins.name << " <frame code> " << std::flush;

    std::stringstream ss;
    ss << std::hex << std::showbase << "addr2line -e " << ins.name << " " << (ins.offset + ins.rip - ins.start);
    system(ss.str().c_str());
    return true;
}

int main(int argc, char **argv)
{
    std::cout << std::hex << std::showbase;

    if (argc <= 1) {
        std::cout << argv[0] << " path/to/exe" << std::endl;
        return 0;
    }

    pid = vfork();
    if (pid < 0) {
        std::cout << "unexpected: vfork failed with " << pid << std::endl;
    }

    if (pid == 0) {
        ptrace(PT_TRACE_ME, 0, 0, 0);
        std::cout << "running: " << argv[1] << "\n\n";
        execl(argv[1], argv[1], nullptr);
        return 0;
    }

    int status;
    wait(&status);
    if (WIFEXITED(status)) {
        std::cout << "unexpected: exit immediately" << std::endl;
        return 0;
    }
    ptrace(PTRACE_CONT, pid, nullptr, nullptr);
    wait(&status);
    if (WIFEXITED(status)) {
        std::cout << "exit normally" << std::endl;
        return 0;
    }
    std::cout << "\ncrashed\n\n";

    struct user_regs_struct regs;
    ptrace(PTRACE_GETREGS, pid, nullptr, &regs);
    int frameid = 0;
    if (printSource(regs.rip, frameid++) == false) {
        return 1;
    }
    for (uint64_t rbp = regs.rbp; rbp != 0; rbp = ptrace(PTRACE_PEEKDATA, pid, rbp, NULL)) {
        if (printSource(ptrace(PTRACE_PEEKDATA, pid, rbp + 8, NULL) - 1, frameid++) == false) {
            return 1;
        }
    }
    return 0;
}
